all repos — caroster @ 2934f49713e078b9c820ecd136e8016eca67d2bf

[Octree] Group carpool to your event https://caroster.io

frontend/pages/e/[uuid]/details.tsx (view raw)

  1import moment from 'moment';
  2import Linkify from 'linkify-react';
  3import Tooltip from '@mui/material/Tooltip';
  4import IconButton from '@mui/material/IconButton';
  5import Box from '@mui/material/Box';
  6import Link from '@mui/material/Link';
  7import Card from '@mui/material/Card';
  8import Container from '@mui/material/Container';
  9import TextField from '@mui/material/TextField';
 10import Typography from '@mui/material/Typography';
 11import TuneIcon from '@mui/icons-material/Tune';
 12import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 13import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
 14import {useTheme} from '@mui/material/styles';
 15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 16import {PropsWithChildren, useState} from 'react';
 17import {useTranslation} from 'next-i18next';
 18import pageUtils from '../../../lib/pageUtils';
 19import DetailsLink from '../../../containers/DetailsLink';
 20import ShareEvent from '../../../containers/ShareEvent';
 21import PlaceInput from '../../../containers/PlaceInput';
 22import LangSelector from '../../../components/LangSelector';
 23import usePermissions from '../../../hooks/usePermissions';
 24import useEventStore from '../../../stores/useEventStore';
 25import useToastStore from '../../../stores/useToastStore';
 26import EventLayout, {TabComponent} from '../../../layouts/Event';
 27import {
 28  EventByUuidDocument,
 29  useUpdateEventMutation,
 30} from '../../../generated/graphql';
 31
 32interface Props {
 33  eventUUID: string;
 34  announcement?: string;
 35}
 36
 37const Page = (props: PropsWithChildren<Props>) => {
 38  return <EventLayout {...props} Tab={DetailsTab} />;
 39};
 40
 41const DetailsTab: TabComponent<Props> = ({}) => {
 42  const {t} = useTranslation();
 43  const {
 44    userPermissions: {canEditEventDetails},
 45  } = usePermissions();
 46  const theme = useTheme();
 47  const [updateEvent] = useUpdateEventMutation();
 48  const addToast = useToastStore(s => s.addToast);
 49  const setEventUpdate = useEventStore(s => s.setEventUpdate);
 50  const event = useEventStore(s => s.event);
 51  const [isEditing, setIsEditing] = useState(false);
 52
 53  if (!event) return null;
 54
 55  const hasGeoloc = event.latitude && event.longitude;
 56
 57  const onSave = async e => {
 58    try {
 59      const {uuid, ...data} = event;
 60      const {
 61        id,
 62        travels,
 63        waitingPassengers,
 64        __typename,
 65        administrators,
 66        passengers,
 67        ...input
 68      } = data;
 69      await updateEvent({
 70        variables: {
 71          uuid,
 72          eventUpdate: {
 73            ...input,
 74          },
 75        },
 76        refetchQueries: ['eventByUUID'],
 77      });
 78      setIsEditing(false);
 79    } catch (error) {
 80      console.error(error);
 81      addToast(t('event.errors.cant_update'));
 82    }
 83  };
 84
 85  const modifyButton = isEditing ? (
 86    <Tooltip
 87      title={t('event.details.save')}
 88      sx={{
 89        position: 'absolute',
 90        top: theme.spacing(2),
 91        right: theme.spacing(2),
 92      }}
 93    >
 94      <IconButton color="primary" onClick={onSave}>
 95        <CheckCircleOutlineIcon />
 96      </IconButton>
 97    </Tooltip>
 98  ) : (
 99    <Tooltip
100      title={t('event.details.modify')}
101      sx={{
102        position: 'absolute',
103        top: theme.spacing(2),
104        right: theme.spacing(2),
105      }}
106    >
107      <IconButton color="primary" onClick={() => setIsEditing(true)}>
108        <TuneIcon />
109      </IconButton>
110    </Tooltip>
111  );
112
113  return (
114    <Box
115      sx={{
116        position: 'relative',
117      }}
118    >
119      <Container
120        sx={{
121          p: 4,
122          mt: 6,
123          mb: 11,
124          mx: 0,
125          [theme.breakpoints.down('md')]: {
126            p: 2,
127          },
128        }}
129      >
130        <Card
131          sx={{
132            position: 'relative',
133            maxWidth: '100%',
134            width: '480px',
135            p: 2,
136          }}
137        >
138          <Typography variant="h4" pb={2}>
139            {t('event.details')}
140          </Typography>
141          {canEditEventDetails() && modifyButton}
142          {(isEditing || event.name) && (
143            <Box pt={2} pr={1.5}>
144              <Typography variant="overline">
145                {t('event.fields.name')}
146              </Typography>
147              <Typography>
148                {isEditing ? (
149                  <TextField
150                    size="small"
151                    fullWidth
152                    value={event.name}
153                    onChange={e => setEventUpdate({name: e.target.value})}
154                    name="name"
155                    id="EditEventName"
156                  />
157                ) : (
158                  <Typography id="EventName">{event.name}</Typography>
159                )}
160              </Typography>
161            </Box>
162          )}
163          {(isEditing || event.date) && (
164            <Box pt={2} pr={1.5}>
165              <Typography variant="overline">
166                {t('event.fields.date')}
167              </Typography>
168              {isEditing ? (
169                <Typography>
170                  <DatePicker
171                    slotProps={{
172                      textField: {
173                        size: 'small',
174                        id: `EditEventDate`,
175                        fullWidth: true,
176                        placeholder: t('event.fields.date_placeholder'),
177                      },
178                    }}
179                    format="DD/MM/YYYY"
180                    value={moment(event.date)}
181                    onChange={date =>
182                      setEventUpdate({
183                        date: !date ? null : moment(date).format('YYYY-MM-DD'),
184                      })
185                    }
186                  />
187                </Typography>
188              ) : (
189                <Box position="relative">
190                  <Typography id="EventDate">
191                    {moment(event.date).format('DD/MM/YYYY')}
192                  </Typography>
193                </Box>
194              )}
195            </Box>
196          )}
197          {(isEditing || event.address) && (
198            <Box pt={2} pr={1.5}>
199              <Typography variant="overline">
200                {t('event.fields.address')}
201              </Typography>
202              {isEditing ? (
203                <PlaceInput
204                  place={event.address}
205                  latitude={event.latitude}
206                  longitude={event.longitude}
207                  onSelect={({place, latitude, longitude}) =>
208                    setEventUpdate({
209                      address: place,
210                      latitude,
211                      longitude,
212                    })
213                  }
214                />
215              ) : (
216                <Box position="relative">
217                  <Typography
218                    id="EventAddress"
219                    title={t`placeInput.noCoordinates`}
220                    sx={{
221                      pr: 3,
222                      display: 'inline-flex',
223                      alignItems: 'center',
224                      columnGap: 1,
225                    }}
226                  >
227                    <Link
228                      target="_blank"
229                      rel="noreferrer"
230                      href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
231                        event.address
232                      )}`}
233                      onClick={e => e.preventDefault}
234                    >
235                      {event.address}
236                    </Link>
237                    {!hasGeoloc && (
238                      <InfoOutlinedIcon fontSize="small" color="warning" />
239                    )}
240                  </Typography>
241                </Box>
242              )}
243            </Box>
244          )}
245          {(isEditing || event.description) && (
246            <Box pt={2} pr={1.5}>
247              <Typography variant="overline">
248                {t('event.fields.description')}
249              </Typography>
250              {isEditing ? (
251                <Typography>
252                  <TextField
253                    fullWidth
254                    multiline
255                    maxRows={4}
256                    inputProps={{maxLength: 250}}
257                    value={event.description || ''}
258                    onChange={e =>
259                      setEventUpdate({description: e.target.value})
260                    }
261                    id={`EditEventDescription`}
262                    name="description"
263                  />
264                </Typography>
265              ) : (
266                <Typography
267                  id="EventDescription"
268                  sx={{pr: 3, whiteSpace: 'pre-line'}}
269                >
270                  <Linkify options={{render: DetailsLink}}>
271                    {event.description}
272                  </Linkify>
273                </Typography>
274              )}
275            </Box>
276          )}
277          {(isEditing || event.lang) && (
278            <Box pt={2} pr={1.5}>
279              <Typography variant="overline">
280                {t('event.fields.lang')}
281              </Typography>
282              {isEditing ? (
283                <LangSelector
284                  value={event.lang}
285                  onChange={lang => setEventUpdate({lang})}
286                />
287              ) : (
288                <Typography id="EventLang" sx={{pr: 3}}>
289                  {t(`PROTECTED.languages.${event.lang}`)}
290                </Typography>
291              )}
292            </Box>
293          )}
294          {!isEditing && (
295            <ShareEvent
296              title={`Caroster ${event.name}`}
297              sx={{width: '100%', mt: 2}}
298            />
299          )}
300        </Card>
301      </Container>
302    </Box>
303  );
304};
305
306export const getServerSideProps = pageUtils.getServerSideProps(
307  async (context, apolloClient) => {
308    const {uuid} = context.query;
309    const {host = ''} = context.req.headers;
310    let event = null;
311
312    // Fetch event
313    try {
314      const {data} = await apolloClient.query({
315        query: EventByUuidDocument,
316        variables: {uuid},
317      });
318      event = data?.eventByUUID?.data;
319    } catch (error) {
320      return {
321        notFound: true,
322      };
323    }
324
325    return {
326      props: {
327        eventUUID: uuid,
328        metas: {
329          title: event?.attributes?.name || '',
330          url: `https://${host}${context.resolvedUrl}`,
331        },
332      },
333    };
334  }
335);
336export default Page;